package org.budo.support.java.awt.robot;

import java.awt.Dimension;
import java.awt.GraphicsDevice;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.budo.support.spring.aop.util.AopUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author limingwei
 */
public class BudoRobotImpl implements BudoRobot {
    private static final Logger log = LoggerFactory.getLogger(BudoRobotImpl.class);

    private Map<GraphicsDevice, Robot> deviceRobotMap = new ConcurrentHashMap<GraphicsDevice, Robot>();

    private BudoRobotImpl _this() {
        return AopUtil.proxy(this);
    }

    @Override
    public int getScreenWidth() {
        Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
        return (int) dimension.getWidth();
    }

    @Override
    public int getScreenHeight() {
        Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
        return (int) dimension.getHeight();
    }

    @Override
    public Point mousePoint() {
        PointerInfo mousePointer = MouseInfo.getPointerInfo();
        return mousePointer.getLocation();
    }

    @Override
    public int rgb(Point point) {
        BudoImage image = this.screenCapture((int) point.getX(), (int) point.getY(), 1, 1);
        return image.rgb(0, 0);
    }

    @Override
    public int rgb(Point point, GraphicsDevice device) {
        BudoImage image = this.screenCapture((int) point.getX(), //
                (int) point.getY(), //
                1, //
                1, device);
        return image.rgb(0, 0);
    }

    @Override
    public int mousePointRgb() {
        Point point = this.mousePoint();
        return this.rgb(point);
    }

    @Override
    public GraphicsDevice mousePointerDevice() {
        PointerInfo mousePointer = MouseInfo.getPointerInfo();
        return mousePointer.getDevice();
    }

    public BudoImage screenCapture() {
        int w = this.getScreenWidth();
        int h = this.getScreenHeight();
        return this.screenCapture(0, 0, w, h);
    }

    @Override
    public BudoImage screenCapture(Rectangle rectangle) {
        int x = (int) rectangle.getX();
        int y = (int) rectangle.getY();
        int w = (int) rectangle.getWidth();
        int h = (int) rectangle.getHeight();

        return _this().screenCapture(x, y, w, h);
    }

    @Override
    public BudoImage screenCapture(Point topLeft, Point bottomRight) {
        double x = topLeft.getX();
        double y = topLeft.getY();

        double w = bottomRight.getX() - x;
        double h = bottomRight.getY() - y;

        return this.screenCapture((int) x, (int) y, (int) w, (int) h);
    }

    @Override
    public BudoImage screenCapture(int x, int y, int w, int h, GraphicsDevice graphicsDevice) {
        int x1 = x, y1 = y, w1 = w, h1 = h;
        if (x < 0) {
            x1 = 0;
        }

        if (y < 0) {
            y1 = 0;
        }

        int _w = this.getScreenWidth();
        int _h = this.getScreenHeight();

        if (w + x1 > _w) {
            w1 = _w - x1;
        }

        if (h + y1 > _h) {
            h1 = _h - y1;
        }

        if (w1 < 0) {
            log.error("#127 return null, x=" + x + ", y=" + y + ", w=" + w + ", h=" + h //
                    + ", x1=" + x1 + ", y1=" + y1 + ", w1" + w1 + ", h1=" + h1 //
                    + ", _w=" + _w + ", _h=" + _h + ", graphicsDevice=" + graphicsDevice);
            return null;
        }

        Rectangle rectangle = new Rectangle(x1, y1, w1, h1);
        try {
            Robot awtRobot = this.getAwtRobot(graphicsDevice);
            BufferedImage bufferedImage = awtRobot.createScreenCapture(rectangle);
            return new BudoImageImpl(bufferedImage);
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("#70 error=" + e + ", rectangle=" + rectangle //
                    + ", x=" + x + ", y=" + y + ", w=" + w + ", h=" + h //
                    + ", screenWidth=" + _w + ", screenHeight=" + _h, e);
        }
    }

    @Override
    public BudoImage screenCapture(int x, int y, int w, int h) {
        GraphicsDevice mousePointerDevice = this.mousePointerDevice();
        return this.screenCapture(x, y, w, h, mousePointerDevice);
    }

    @Override
    public BudoRobot mouseMove(int x, int y) {
        this.getAwtRobot().mouseMove(x, y);
        return this;
    }

    @Override
    public BudoRobot mouseMove(Point point) {
        return this.mouseMove((int) point.getX(), (int) point.getY());
    }

    @Override
    public BudoRobot mouseWheel(int wheelAmt) {
        this.getAwtRobot().mouseWheel(wheelAmt);
        return this;
    }

    @Override
    public BudoRobot delay(int ms) {
        this.getAwtRobot().delay(ms);
        return this;
    }

    @Override
    public BudoRobot mouseClick(int key) {
        this.getAwtRobot().mousePress(key);
        this.getAwtRobot().mouseRelease(key);
        return this;
    }

    private BudoRobot keyClick(int key) {
        this.getAwtRobot().keyPress(key);
        this.getAwtRobot().keyRelease(key);
        return this;
    }

    @Override
    public BudoRobot clickLeft() {
        return this.mouseClick(KeyEvent.BUTTON1_MASK);
    }

    @Override
    public BudoRobot clickRight() {
        return this.mouseClick(KeyEvent.BUTTON3_MASK);
    }

    @Override
    public BudoRobot clickCtrl() {
        return this.keyClick(KeyEvent.VK_CONTROL);
    }

    @Override
    public BudoRobot pressLeft() {
        this.getAwtRobot().mousePress(KeyEvent.BUTTON1_MASK);
        return this;
    }

    @Override
    public BudoRobot releaseLeft() {
        this.getAwtRobot().mouseRelease(KeyEvent.BUTTON1_MASK);
        return this;
    }

    @Override
    public BudoRobot CTRL_C() {
        this.getAwtRobot().keyPress(KeyEvent.VK_CONTROL);
        this.getAwtRobot().keyPress(KeyEvent.VK_C);
        this.getAwtRobot().keyRelease(KeyEvent.VK_C);
        this.getAwtRobot().keyRelease(KeyEvent.VK_CONTROL);
        return this;
    }

    @Override
    public String getClipboardContents() {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable transferable = clipboard.getContents(null);

        if (null == transferable) {
            log.error("transferable is null");
            return null;
        }

        if (!transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) {
            log.error("not support string");
            return null;
        }

        try {
            return (String) transferable.getTransferData(DataFlavor.stringFlavor);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    private Robot getAwtRobot() {
        GraphicsDevice mousePointerDevice = this.mousePointerDevice();
        return this.getAwtRobot(mousePointerDevice);
    }

    private Robot getAwtRobot(GraphicsDevice device) {
        Robot robot = deviceRobotMap.get(device);
        if (null != robot) {
            return robot;
        }

        robot = this.initAwtRobot(device);
        deviceRobotMap.put(device, robot);
        return robot;
    }

    private Robot initAwtRobot(GraphicsDevice graphicsDevice) {
        try {
//            GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
//            GraphicsDevice[] graphicsDevices = graphicsEnvironment.getScreenDevices(); // 多块屏幕
            Robot robot = new Robot(graphicsDevice);

            log.info("#250 initAwtRobot, graphicsDevice=" + graphicsDevice + ", robot=" + robot);
            return robot;
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public BudoRobot doubleClickLeft() {
        return this.clickLeft() //
                .clickLeft();
    }
}